package com.szboanda.parser.impl;

import com.szboanda.parser.ISqlParser;
import com.szboanda.parser.SqlContext;
import com.szboanda.parser.SqlParserException;
import org.apache.commons.lang.StringUtils;


import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author ：zhangsheng
 * @date ：Created in 2020/2/22 12:18
 * @description：sql绑定参数解析，例如 ${..}、#{parame}
 * @modified By：
 * @version: 1.0$
 */
public class SqlBindingParamParserImpl implements ISqlParser {


    /**
     * 日期表达式中的硬变量替换,例如：${ now + <daysNum>d}中的daysNum替换为环境变量中的参数，必须为整性
     */
    private static final Pattern HARD_PARAM_PATTERN = Pattern.compile("<(?<value>.*?)>");


    /**
     * 脚本binding参数匹配： #{parame} 以及  ${ now -1y - 1M - 1d + 2h + 2m }
     */
    private static final Pattern SCRIPT_PARAM_PATTERN = Pattern.compile("\\#\\{\\s*?(?<startParam>.*?)\\s*?\\}|\\$\\{\\s*?(?<timeExpresion>.*?)\\s*?\\}");


    /**
     * 單位：匹配${ now -1y - 1M - 1d + 2h + 2m }中的 1y
     */
    private static final Pattern OPERATE_NUM = Pattern.compile("([+-])\\s?(?<m>\\d{1,})m|([+-])\\s?(?<h>\\d{1,})h|([+-])\\s?(?<d>\\d{1,})d|([+-])\\s?(?<w>\\d{1,})w|([+-])\\s?(?<M>\\d{1,})M|([+-])\\s?(?<y>\\d{1,})y");


    /**
     * 对象：匹配${ now -1y - 1M - 1d + 2h + 2m }中的 now/minute/hour/today/month/yeay/plantime
     */
    private static final Pattern OBJECT_PATTERN = Pattern.compile("(?<value>now|minute|hour|today|week|month|year)");


    /**
     * 日期偏移单位
     */
    private static final Map<String,Integer> unitCalendar = new HashMap<String,Integer>();

    static {
        unitCalendar.put("m", Calendar.MINUTE);
        unitCalendar.put("h", Calendar.HOUR);
        unitCalendar.put("d", Calendar.DATE);
        unitCalendar.put("w", Calendar.WEEK_OF_YEAR);
        unitCalendar.put("M", Calendar.MONTH);
        unitCalendar.put("y", Calendar.YEAR);
    }

    /**
     *
     * @param sqlScript 待解析的sql脚本
     * @param sqlArgs  解析后的变量存放容器
     * @param context  解析环境
     * @return
     */
    public String parse(String sqlScript, List<Object> sqlArgs, Map<String, ?> context){
        Map<String, String> replacerMap = new HashMap<String, String>();
        Matcher match = SCRIPT_PARAM_PATTERN.matcher(sqlScript);
        while (match.find()) {
            String key = match.group(0);
            if (key.startsWith("$")) {//日期表达式
                String value = match.group("timeExpresion").trim();
                replacerMap.put(key, "?");
                try {
                    sqlArgs.add(this.parseDate(value, context));
                } catch (SqlParserException e) {
                    throw new SqlParserException(String.format("获取绑定参数异常sqlScript=%s;var=%s;\n", sqlScript, value), e);
                }
            } else {//普通参数
                String value = match.group("startParam").trim();
                if (null == context.get(value)) {
                    throw new SqlParserException(String.format("\"获取绑定参数异常，缺少参数%s\n", value));
                } else {
                    Object realValue = context.get(value);
                    if (realValue instanceof List) {
                        List<?> vals = (List)realValue;
                        sqlArgs.addAll(vals);
                        String manyArgs = "(" +  StringUtils.join(Collections.nCopies(vals.size(), "?"), ',') + ")";
                        replacerMap.put(key, manyArgs);
                    } else if (realValue.getClass().isArray()) {
                        Object[] vals = (Object[])realValue;
                        sqlArgs.addAll(Arrays.asList(vals));
                        String manyArgs = "(" +  StringUtils.join(Collections.nCopies(vals.length, "?"), ',') + ")";
                        replacerMap.put(key, manyArgs);
                    } else {
                        replacerMap.put(key, "?");
                        sqlArgs.add(realValue);
                    }
                }
            }
        }
        //占位符sql语句
        for (String replace :  replacerMap.keySet()) {
            sqlScript = sqlScript.replace(replace, replacerMap.get(replace));
        }
        return sqlScript;
    }

    /**
     * 日期表达式解析
     * @param value 表达式
     * @param 日期相关参数
     * @return
     */
    private Date parseDate(String value, Map<String, ?> autowrriteParam) throws SqlParserException {
        String strDate = value;
        // 日期表达式中的硬变量替换,例如：${ now + <daysNum>d}中的daysNum替换为环境变量中的参数，必须为整性
        Map<String, String> hardParam = this.getTags(value, HARD_PARAM_PATTERN);
        if (!hardParam.isEmpty()) {
            for(Map.Entry<String, String> entry : hardParam.entrySet()) {
                Object hardValue = autowrriteParam.get(entry.getValue());
                if (null == hardValue) {
                    throw new SqlParserException(String.format("无法获取时间表达式【%s】中硬变量【%s】,不存在该变量", value, entry.getKey()));
                } else {
                    if (hardValue instanceof String) {
                        try {
                            Integer.parseInt(hardValue.toString());
                            strDate = value.replace(entry.getKey(), hardValue.toString());
                        } catch (NumberFormatException e) {
                            throw new SqlParserException(String.format("时间表达式【%s】中硬变量【%s】,应为整形，实际值为【%s】", value, entry.getKey(), hardValue));
                        }
                    } else if (Integer.class.equals(hardValue.getClass()) || int.class.equals(hardValue.getClass())) {
                        strDate = value.replace(entry.getKey(), ((Integer)hardValue).toString());
                    } else {
                        throw new SqlParserException(String.format("时间表达式【%s】中硬变量【%s】,应为整形，实际值为【%s】", value, entry.getKey(), hardValue));
                    }
                }
            }
        }
        //解析时间变量
        Map<String, String> objects = getTags(strDate, OBJECT_PATTERN);
        if (objects.size() < 1) {
            throw new SqlParserException(String.format("时间表达式【%s】缺少时间对象,例如:now、minute、hour、today、week、month、year", strDate));
        } else if (objects.size() > 1) {
            throw new SqlParserException(String.format("时间表达式【%s】语法错误，仅支持一个时间对象", strDate));
        }
        String objName = objects.keySet().iterator().next();
        Calendar calendar = this.getDateObject(objName);
        //解析偏移值
        Matcher match = OPERATE_NUM.matcher(strDate);
        while (match.find()) {
            String scriptKey = match.group(0);
            String unitKey = scriptKey.substring(scriptKey.length() -1);
            String strNum = match.group(unitKey);
            String operarte = "+";
            for (int i = 1; i < match.groupCount(); i++) {
                if ("+".equals(match.group(i)) || "-".equals(match.group(i))) {
                    operarte = match.group(i);
                    break;
                }
            }
            if ("+".equals(operarte)) {
                calendar.add(unitCalendar.get(unitKey), Integer.parseInt(strNum));
            } else {
                calendar.add(unitCalendar.get(unitKey), - 1 * Integer.parseInt(strNum));
            }
        }
        return new Date(calendar.getTimeInMillis());
    }


    /**
     * 获取标签
     * @param source 原文
     * @param pattern 匹配模式
     * @return
     */
    private Map<String, String> getTags(String source, Pattern pattern) {
        Map<String, String> map = new HashMap<String, String>();
        Matcher match = pattern.matcher(source);
        while (match.find()) {
            String key = match.group(0);
            String value = match.group("value");
            map.put(key, value.trim());
        }
        return map;
    }


    /**
     * 获取变量信息
     * 当前精确时间、整分、整时、整日、整月、整年
     * @param objName
     * @return
     * @throws SqlParserException
     */
    private Calendar getDateObject(String objName) throws SqlParserException {
        Calendar calendar = Calendar.getInstance();
        if ("now".equals(objName)) {
            return calendar;
        }
        //毫秒数、秒数归零
        calendar.set(Calendar.MILLISECOND, 0);
        calendar.set(Calendar.SECOND, 0);
        if ("minute".equals(objName)) {
            return calendar;
        }
        //分钟数归零
        calendar.set(Calendar.MINUTE, 0);
        if ("hour".equals(objName)) {
            return calendar;
        }
        //小时数归零
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        if ("today".equals(objName)) {
            return calendar;
        }
        if ("week".equals(objName)) {
            //归到周一,这个要放到里面
            calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
            return calendar;
        }
        //当月第一天
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        if ("month".equals(objName)) {
            return calendar;
        }
        if ("year".equals(objName)) {
            //当年第一月
            calendar.set(Calendar.MONTH, Calendar.JANUARY);
            return calendar;
        }
        throw new SqlParserException(String.format("暂不支持时间对象【%s】", objName));
    }

}
